const jsonwebtoken_1 = require("jsonwebtoken");
const exception_1 = require("./exception");
const lodash_1 = require("lodash");
const { TokenType } = require("../app/lib/enums");
const conf = require('../config/config');
const { User } = require('../app/models/make/user');
const { getNowTime } = require('./util');
/**
 * 令牌类，提供令牌的生成和解析功能
 */
class Token {
    /**
     * 构造函数
     * @param secret 牌的secret值
     * @param accessExp access token 过期时间
     * @param refreshExp refresh token 过期时间
     */
    constructor(secret, accessExp, refreshExp) {
        /**
         * access token 默认的过期时间
         */
        this.accessExp = 60 * 60; // 1h;
        /**
         * refresh token 默认的过期时间
         */
        this.refreshExp = 60 * 60 * 24 * 30 * 3; // 3 months
        secret && (this.secret = secret);
        refreshExp && (this.refreshExp = refreshExp);
        accessExp && (this.accessExp = accessExp);
    }
    /**
     * 挂载到 ctx 上
     */
    initApp(app, secret, accessExp, refreshExp) {
        // 将jwt实例挂到app的context上
        app.context.jwt = this;
        secret && (this.secret = secret);
        refreshExp && (this.refreshExp = refreshExp);
        accessExp && (this.accessExp = accessExp);
    }
    /**
     * 生成access_token
     * @param identity 标识位
     */
    createAccessToken(identity) {
        if (!this.secret) {
            throw new Error('密匙不可为空');
        }
        let exp = Math.floor(Date.now() / 1000) + this.accessExp;
        return jsonwebtoken_1.sign({
            exp: exp,
            identity: identity,
            scope: 'make',
            type: TokenType.ACCESS
        }, this.secret);
    }
    /**
     * 生成refresh_token
     * @param identity 标识位
     */
    createRefreshToken(identity) {
        if (!this.secret) {
            throw new Error('密匙不可为空');
        }
        let exp = Math.floor(Date.now() / 1000) + this.refreshExp;
        return jsonwebtoken_1.sign({
            exp: exp,
            identity: identity,
            scope: 'make',
            type: TokenType.REFRESH
        }, this.secret);
    }
    /**
     * verifyToken 验证token
     * 若过期，抛出ExpiredTokenException
     * 若失效，抛出InvalidTokenException
     *
     * @param token 令牌
     */
    verifyToken(token) {
        if (!this.secret) {
            throw new Error('密匙不可为空');
        }
        // NotBeforeError
        // TokenExpiredError
        let decode;
        try {
            decode = jsonwebtoken_1.verify(token, this.secret);
        }
        catch (error) {
            if (error instanceof jsonwebtoken_1.TokenExpiredError) {
                throw new exception_1.ExpiredTokenException();
            }
            else {
                throw new exception_1.InvalidTokenException();
            }
        }
        return decode;
    }
}

/**
 * jwt 的实例
 */
const jwt = new Token(conf.security.secret, conf.security.accessExp, conf.security.refreshExp);

/**
 * 生成access token
 * @param payload 负载，支持 string 和 object
 * @param options 参数
 */
function createAccessToken(payload, options) {
    // type: TokenType.REFRESH
    let exp = Math.floor(Date.now() / 1000) + jwt.accessExp;
    if (typeof payload === 'string') {
        return jsonwebtoken_1.sign({ indentify: payload, type: TokenType.ACCESS, exp: jwt.accessExp }, jwt.secret, options);
    }
    else {
        return jsonwebtoken_1.sign(Object.assign(Object.assign({}, payload), { type: TokenType.ACCESS, exp: exp }), jwt.secret, options);
    }
}

/**
 * 生成refresh token
 * @param payload 负载，支持 string 和 object
 * @param options 参数
 */
function createRefreshToken(payload, options) {
    let exp = Math.floor(Date.now() / 1000) + jwt.refreshExp;
    // type: TokenType.REFRESH
    if (typeof payload === 'string') {
        return jsonwebtoken_1.sign({ indentify: payload, type: TokenType.REFRESH, exp: jwt.refreshExp }, jwt.secret, options);
    }
    else {
        return jsonwebtoken_1.sign(Object.assign(Object.assign({}, payload), { type: TokenType.REFRESH, exp: exp }), jwt.secret, options);
    }
}

/**
 * 验证 access token
 * @param token 令牌
 * @param options 选项
 */
function verifyAccessToken(token, options) {
    let decode;
    try {
        decode = jsonwebtoken_1.verify(token, jwt.secret, options);
    }
    catch (error) {
        if (error instanceof jsonwebtoken_1.TokenExpiredError) {
            throw new exception_1.ExpiredTokenException();
        }
        else {
            throw new exception_1.InvalidTokenException();
        }
    }
    if (!decode['type'] || decode['type'] !== TokenType.ACCESS) {
        throw new exception_1.InvalidTokenException({ errMsg: '令牌类型错误' });
    }
    return decode;
}

/**
 * 验证 refresh token
 * @param token 令牌
 * @param options 选项
 */
function verifyRefreshToken(token, options) {
    let decode;
    try {
        decode = jsonwebtoken_1.verify(token, jwt.secret, options);
    }
    catch (error) {
        if (error instanceof jsonwebtoken_1.TokenExpiredError) {
            throw new exception_1.ExpiredTokenException();
        }
        else {
            throw new exception_1.InvalidTokenException();
        }
    }
    if (!decode['type'] || decode['type'] !== TokenType.REFRESH) {
        throw new exception_1.InvalidTokenException({ errMsg: '令牌类型错误' });
    }
    return decode;
}

/**
 * 颁发令牌
 * @param user 用户
 */
function getTokens(user) {
    const accessToken = jwt.createAccessToken(user.uid);
    const refreshToken = jwt.createRefreshToken(user.uid);
    return { accessToken, refreshToken };
}

/**
 * 解析请求头
 * @param ctx koa 的context
 * @param type 令牌的类型
 */
async function parseHeader(ctx, type = TokenType.ACCESS) {
    // 此处借鉴了koa-jwt
    if (!ctx.header || !ctx.header.authorization) {
        ctx.throw(new exception_1.AuthFailed({ errMsg: '认证失败，未登录或令牌错误' }));
    }
    const parts = ctx.header.authorization.split(' ');
    if (parts.length === 2) {
        // Bearer 字段
        const scheme = parts[0];
        // token 字段
        const token = parts[1];
        if (/^Bearer$/i.test(scheme)) {
            // @ts-ignore
            const obj = ctx.jwt.verifyToken(token);
            if (!lodash_1.get(obj, 'type') || lodash_1.get(obj, 'type') !== type) {
                ctx.throw(new exception_1.AuthFailed({ errMsg: '请使用正确类型的令牌' }));
            }
            if (!lodash_1.get(obj, 'scope') || lodash_1.get(obj, 'scope') !== 'make') {
                ctx.throw(new exception_1.AuthFailed({ errMsg: '请使用正确作用域的令牌' }));
            }
            // @ts-ignore
            const user = await User.findOne({
                where: {
                    uid: lodash_1.get(obj, 'identity')
                },
                attributes: { exclude: ['id', 'password', 'ip', 'updated_at', 'deleted_at'] }
            });
            if (!user) {
              ctx.throw(new exception_1.NotFound({ errMsg: '用户不存在' }));
            }
            if (user.getDataValue('last_at') != "") {
                user.setDataValue('last_at', getNowTime(user.getDataValue('last_at')))
            }
            user.setDataValue('created_at', getNowTime(user.getDataValue('created_at')))
            // 将user挂在ctx上
            // @ts-ignore
            ctx.userInfo = user;
        }
    }
    else {
        ctx.throw(new exception_1.AuthFailed());
    }
}

/**
 * 守卫函数，验证用户是否登录、令牌是否正确
 */
async function tokenRequired(ctx, next) {
    // 添加access 和 refresh 的标识位
    if (ctx.request.method !== 'OPTIONS') {
        await parseHeader(ctx, TokenType.ACCESS);
        await next();
    }
    else {
        await next();
    }
}

module.exports = {
    Token,
    jwt,
    createAccessToken,
    createRefreshToken,
    verifyAccessToken,
    verifyRefreshToken,
    getTokens,
    parseHeader,
    tokenRequired
}
